/*
 * SyncBlock.cpp
 *
 *  Created on: Apr 19, 2013
 *      Author: Lucifer
 */

#include "../pch.h"
#include "SyncBlock.h"
#include "REFThread.h"

#ifdef	_WIN32
#else	//	_WIN32
#include <unistd.h>	/* usleep */
#include <signal.h>
#include <pthread.h>
#include <sys/time.h>
#include <errno.h>

#endif	//	!_WIN32

namespace BFX
{

#ifdef	_WIN32

SyncBlock::SyncBlock()
{
	m_nOwningThreadId = 0L;
	m_nLockCount = 0;
}

SyncBlock::~SyncBlock()
{
	BFX_ASSERT(m_listEvents.GetSize() == 0);
}

bool SyncBlock::Wait(INT32 nMilliseconds)
{
	BFX_ENSURE1(m_nOwningThreadId == ::GetCurrentThreadId(), "The thread attempt to wait on an sync-block without owning it.");

	REFThread* pCurThread = REFThread::GetCurrentThread();
	if (pCurThread)
		return pCurThread->DoAppropriateWait(this, nMilliseconds);
	else
		return WaitNoAlert(nMilliseconds);
}

bool SyncBlock::WaitNoAlert(INT32 nMilliseconds)
{
	BFX_ASSERT(nMilliseconds >= 0 || nMilliseconds == (INT32)INFINITE);

	bool bAlertable = true;

	// Enter a new event handle into the wait set.
	// Create the new event.
	HANDLE hWaitEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
	BFX_ASSERT(hWaitEvent != NULL);
	EnqueueWaitEvent(hWaitEvent);

	// Store the current lock count for re-acquisition.
	int nThisThreadsLockCount = m_nLockCount;
	m_nLockCount = 0;

	// Release the synchronization lock the appropriate number of times.
	// Win32 allows no error checking here.
	for( int i = 0; i < nThisThreadsLockCount; i ++)
		m_mutexWait.Unlock();

	DWORD dwWaitResult = WAIT_FAILED;
	// Wait for the event
	dwWaitResult = ::WaitForSingleObjectEx(hWaitEvent, (DWORD)nMilliseconds, bAlertable);

	// However, attempt to remove it form event list.
	MutexHolder holder(&m_lockEvents);
	m_listEvents.Remove(hWaitEvent);
	holder.Release();

	// Acquire the synchronization lock the appropriate number of times.
	// Win32 allows no error checking here.
	for( int j = 0; j < nThisThreadsLockCount; j ++)
		m_mutexWait.Lock();

	// Restore lock count.
	BFX_ASSERT(m_nLockCount == 0);
	m_nLockCount += nThisThreadsLockCount;

	// Close event handle
	BOOL bRetVal = ::CloseHandle(hWaitEvent);
	BFX_ASSERT(bRetVal);

	BFX_ENSURE1(dwWaitResult != WAIT_FAILED, "WaitForSingleObjectEx() failed.");

	return (dwWaitResult == WAIT_TIMEOUT);
}

void SyncBlock::Signal()
{
	BFX_ENSURE1(m_nOwningThreadId == ::GetCurrentThreadId(),
			"The thread attempt to notify other threads waiting on an sync-block without owning it.");

	// Pop the first handle, if any, off the wait set.
	HANDLE hWaitEvent = DequeueWaitEvent();

	// If there is not thread currently waiting, that's just fine.
	if(NULL == hWaitEvent)
		return;

	// Signal the event.
	SetEvent(hWaitEvent);
}

void SyncBlock::SignalAll()
{
	BFX_ENSURE1(m_nOwningThreadId == ::GetCurrentThreadId(),
			"The thread attempt to notify other threads waiting on an sync-block without owning it.");

	// Signal all events on the list, then clear it. Win32 allows no
	// error checking on entering and leaving the critical section.
	//
	MutexHolder lock(&m_lockEvents);

	// Set all events.
	while (m_listEvents.GetSize() != 0)
	{
		HANDLE hWaitEvent = m_listEvents.GetFirst();
		m_listEvents.RemoveFirst();

		BOOL bRetVal = SetEvent(hWaitEvent);
		BFX_ASSERT1(bRetVal, "failed to set event to handle.");
	}
}

void SyncBlock::EnterMonitor()
{
	m_mutexWait.Lock();

	m_nOwningThreadId = ::GetCurrentThreadId();
	m_nLockCount ++;
}

bool SyncBlock::TryEnterMonitor(INT32 nMilliseconds)
{
	if (m_mutexWait.TryLock())
	{
		m_nOwningThreadId = ::GetCurrentThreadId();
		m_nLockCount ++;
		return true;
	}
	return false;
}

void SyncBlock::LeaveMonitor()
{
	m_nLockCount --;
	BFX_ASSERT(m_nLockCount >= 0);
	//if (m_nLockCount == 0)
	//	m_nOwningThreadId = 0;

	m_mutexWait.Unlock();
}

void SyncBlock::EnqueueWaitEvent(HANDLE hWaitEvent)
{
	BFX_ASSERT(hWaitEvent != NULL);

	MutexHolder lock(&m_lockEvents);
	m_listEvents.AddLast(hWaitEvent);
}

HANDLE SyncBlock::DequeueWaitEvent()
{
	MutexHolder lock(&m_lockEvents);

	HANDLE hWaitEvent = NULL;
	if (m_listEvents.GetSize() > 0)
	{
		hWaitEvent = m_listEvents.GetFirst();
		m_listEvents.RemoveFirst();
	}

	return hWaitEvent;
}

#else	//	_WIN32

//////////////////////////////////////////////////////////////////////////////

/**
 * Initializes a new instance of BFX::SyncBlock.
 * @return
 */
SyncBlock::SyncBlock()
{
	m_nOwningThreadId = 0;
	int nRetVal = ::pthread_mutex_init(&m_hMutex, NULL);
	BFX_ASSERT1(nRetVal == 0, "pthread_mutex_init() failed");

	nRetVal = ::pthread_cond_init(&m_hCond, NULL);
	BFX_ASSERT1(nRetVal == 0, "pthread_cond_init() failed.");
}

/**
 * Destroys a instance of BFX::SyncBlock
 * @return
 */
SyncBlock::~SyncBlock()
{
	::pthread_cond_destroy(&m_hCond);
	::pthread_mutex_destroy(&m_hMutex);
}

/**
 * Releases the lock on an object and blocks the current thread until it reacquires the lock. If the specified time-out interval
 * elapses, the thread enters the ready queue.
 * @param[in] nMilliseconds The number of milliseconds to wait before the thread enters the ready queue.
 * @return True if the lock was reacquired before the specified time elapsed; False if the lock was reacquired after the specified time
 * 		elapsed. The method does not return until the lock is reacquired.
 */
bool SyncBlock::Wait(INT32 nMilliseconds)
{
	BFX_ENSURE1(nMilliseconds >= 0 || nMilliseconds == (INT32)INFINITE, "timeout value out of range.");
	BFX_ENSURE1(m_nOwningThreadId == (long)pthread_self(), "The thread attempt to wait on an sync-block without owning it.");

	REFThread* pCurThread = REFThread::GetCurrentThread();
	if (pCurThread)
		return pCurThread->DoAppropriateWait(this, nMilliseconds);
	else
		return WaitNoAlert(nMilliseconds);
}

/**
 * Releases an exclusive lock on the specified object.
 */
bool SyncBlock::WaitNoAlert(INT32 nMilliseconds)
{
	BFX_ASSERT1(nMilliseconds >= 0 || nMilliseconds == (INT32)INFINITE, "timeout value out of range.");
	BFX_ASSERT1(m_nOwningThreadId == (long)pthread_self(), "The thread attempt to wait on an sync-block without owning it.");

	UINT32 dwMilliseconds = (UINT32)nMilliseconds;

	int nRetVal;

	if (nMilliseconds == (INT32) INFINITE)
	{
		nRetVal = ::pthread_cond_wait(&m_hCond, &m_hMutex);
		BFX_ENSURE1(nRetVal == 0, "SyncBlock failed to entering wait state.");
	}
	else
	{
	    timeval tv;
		timespec abstime;

		nRetVal = gettimeofday(&tv, NULL);
		BFX_ASSERT1(nRetVal != -1, "SyncBlock get system time error, wait failed");

		tv.tv_sec += (dwMilliseconds / 1000);
		tv.tv_usec += ((dwMilliseconds % 1000) * 1000);
		if (tv.tv_usec >= 1000000)
		{
			tv.tv_sec++;
			tv.tv_usec -= 1000000;
		}
		abstime.tv_sec = tv.tv_sec;
		abstime.tv_nsec = tv.tv_usec * 1000;

		nRetVal = pthread_cond_timedwait(&m_hCond, &m_hMutex, &abstime);
		if (nRetVal != 0)
		{
			BFX_ENSURE1(nRetVal == ETIMEDOUT, "SyncBlock failed to entering timed wait state");
			return false;
		}
	}

	return true;
}

/**
 * Notifies a thread in the waiting queue of a change in the lock state.
 */
void SyncBlock::Signal()
{
	BFX_ENSURE1(m_nOwningThreadId == (long)::pthread_self(),
			"The thread attempt to notify other threads waiting on an sync-block without owning it.");

	int nRetVal = ::pthread_cond_signal(&m_hCond);
	BFX_ENSURE1(nRetVal == 0, "SyncBlock failed to signal condition object");
}

/**
 * Notifies all waiting threads of a change in the object's state.
 */
void SyncBlock::SignalAll()
{
	BFX_ENSURE1(m_nOwningThreadId == (long)::pthread_self(),
			"The thread attempt to notify other threads waiting on an sync-block without owning it.");

	int nRetVal = ::pthread_cond_broadcast(&m_hCond);
	BFX_ENSURE1(nRetVal == 0, "SyncBlock failed to broadcast to condition object.");
}

/**
 * Acquires an exclusive lock on the specified object.
 */
void SyncBlock::EnterMonitor()
{
	int nRetVal = ::pthread_mutex_lock(&m_hMutex);
	BFX_ENSURE(nRetVal == 0);

	m_nOwningThreadId = (long)::pthread_self();
}

/**
 * Attempts to acquire an exclusive lock on the specified object.
 * @param[in] nMilliseconds The number of milliseconds to wait for the lock. Default value is 0; Specially -1 equivalent to BFX::SyncBlock::EnterMonitor()
 * @return True if the current thread acquires the lock; otherwise, false.
 */
bool SyncBlock::TryEnterMonitor(INT32 nMilliseconds)
{
	int nRetVal = ::pthread_mutex_trylock(&m_hMutex);
	BFX_ENSURE(nRetVal == 0 || nRetVal == EBUSY);
	if (nRetVal == 0)
	{
		m_nOwningThreadId = (long)::pthread_self();
		return true;
	}
	return false;
}

/**
 * Releases an exclusive lock on the specified object.
 */
void SyncBlock::LeaveMonitor()
{
	// TODO: m_nOwningThreadId = 0;
	int nRetVal = ::pthread_mutex_unlock(&m_hMutex);
	BFX_ENSURE(nRetVal == 0);
}

#endif	//	_WIN32

} /* namespace BFX */
